package org.example.pinda.aspect;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.api.R;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.example.pinda.base.CommonResult;
import org.example.pinda.context.BaseContextHandler;
import org.example.pinda.entity.OperateLog;
import org.example.pinda.event.SysLogEvent;
import org.example.pinda.util.LogUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;

/**
 * 系统日志，使用Spring Event异步入库
 */
@Slf4j
@Aspect
public class SysLogAspect {

    @Autowired
    private ApplicationContext applicationContext;

    private static final ThreadLocal<OperateLog> THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 定义切点拦截规则，拦截SysLog注解
     */
    @Pointcut("@annotation(org.example.pinda.annotation.SysLog)")
    public void sysLogAspect() {
    }

    @Before("sysLogAspect()")
    public void recordLog(JoinPoint joinPoint) {
        tryCatch(val -> {
            OperateLog operateLog =
                    get().setCreateUser(BaseContextHandler.getUserId())
                            .setUsername(BaseContextHandler.getName())
                            .setClassPath(joinPoint.getTarget().getClass().getName())
                            .setActionMethod(joinPoint.getSignature().getName());
            // 获取描述信息
            String controllerDesc = getControllerDesc(joinPoint);
            String controllerMethodDescription = LogUtil.getControllerMethodDescription(joinPoint);
            operateLog.setDescription(StrUtil.isEmpty(controllerMethodDescription)
                    ? controllerMethodDescription : controllerDesc);
            // 参数
            Object[] args = joinPoint.getArgs();
            HttpServletRequest request =
                    ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes()))
                            .getRequest();
            String strArgs = "";
            try {
                if (!request.getContentType().contains("multipart/form-data")) {
                    strArgs = JSONObject.toJSONString(args);
                }
            } catch (Exception e) {
                try {
                    strArgs = Arrays.toString(args);
                } catch (Exception ex) {
                    log.warn("解析参数异常", ex);
                }
            }
            operateLog.setParams(getText(strArgs));

            operateLog.setRequestIp(ServletUtil.getClientIP(request))
                    .setRequestUri(URLUtil.getPath(request.getRequestURI()))
                    .setHttpMethod(request.getMethod())
                    .setUa(StrUtil.sub(request.getHeader("user-agent"), 0, 500))
                    .setStartTime(LocalDateTime.now());
            THREAD_LOCAL.set(operateLog);
        });
    }

    /**
     * 返回通知
     */
    @AfterReturning(returning = "ret", pointcut = "sysLogAspect()")
    public void doAfterReturning(Object ret) {
        tryCatch(val -> {
            CommonResult<?> result = Convert.convert(CommonResult.class, ret);
            OperateLog operateLog = get();
            if (result == null) {
                operateLog.setType("OPT");
            } else {
                if (result.getIsSuccess()) {
                    operateLog.setType("OPT");
                } else {
                    operateLog.setType("EX");
                    operateLog.setExDetail(result.getMsg());
                }
                operateLog.setResult(getText(result.toString()));
            }
            publishEvent(operateLog);
        });
    }

    private String getControllerDesc(JoinPoint joinPoint) {
        Api api = joinPoint.getTarget().getClass().getAnnotation(Api.class);
        return Optional.ofNullable(api)
                .map(Api::tags)
                .filter(tags -> tags.length > 0)
                .map(tags -> tags[0])
                .orElse("");
    }

    private void tryCatch(Consumer<String> consumer) {
        try {
            consumer.accept("");
        } catch (Exception e) {
            log.warn("记录操作日志异常", e);
            THREAD_LOCAL.remove();
        }
    }

    private OperateLog get() {
        OperateLog operateLog = THREAD_LOCAL.get();
        return operateLog == null ? new OperateLog() : operateLog;
    }

    /**
     * 截取指定长度的字符串
     */
    private String getText(String val) {
        return StrUtil.sub(val, 0, 65535);
    }

    private void publishEvent(OperateLog operateLog) {
        operateLog.setFinishTime(LocalDateTime.now())
                .setConsumingTime(operateLog.getStartTime()
                        .until(operateLog.getFinishTime(), ChronoUnit.MILLIS));
        applicationContext.publishEvent(new SysLogEvent(operateLog));
        THREAD_LOCAL.remove();
    }

}
